package app

import (
	"context"
	"errors"
	"fmt"

	"github.com/openmeterio/openmeter/openmeter/customer"
	"github.com/openmeterio/openmeter/pkg/models"
	"github.com/openmeterio/openmeter/pkg/pagination"
)

// App represents an installed app
type App interface {
	GetAppBase() AppBase
	GetID() AppID
	GetType() AppType
	GetName() string
	GetDescription() *string
	GetStatus() AppStatus
	GetMetadata() map[string]string
	GetListing() MarketplaceListing

	GetEventAppData() (EventAppData, error)

	UpdateAppConfig(ctx context.Context, input AppConfigUpdate) error

	// ValidateCapabilities validates if the app can run for the given capabilities
	ValidateCapabilities(capabilities ...CapabilityType) error

	// Customer data
	GetCustomerData(ctx context.Context, input GetAppInstanceCustomerDataInput) (CustomerData, error)
	UpsertCustomerData(ctx context.Context, input UpsertAppInstanceCustomerDataInput) error
	DeleteCustomerData(ctx context.Context, input DeleteAppInstanceCustomerDataInput) error
}

type GetAppInstanceCustomerDataInput struct {
	CustomerID customer.CustomerID
}

func (i GetAppInstanceCustomerDataInput) Validate() error {
	if err := i.CustomerID.Validate(); err != nil {
		return err
	}

	return nil
}

type UpsertAppInstanceCustomerDataInput struct {
	CustomerID customer.CustomerID
	Data       CustomerData
}

func (i UpsertAppInstanceCustomerDataInput) Validate() error {
	if err := i.CustomerID.Validate(); err != nil {
		return err
	}

	if err := i.Data.Validate(); err != nil {
		return err
	}

	return nil
}

type DeleteAppInstanceCustomerDataInput struct {
	CustomerID customer.CustomerID
}

func (i DeleteAppInstanceCustomerDataInput) Validate() error {
	if err := i.CustomerID.Validate(); err != nil {
		return err
	}

	return nil
}

// GetAppInput is the input for getting an installed app
type GetAppInput = AppID

type AppConfigUpdate interface {
	models.Validator
}

// UpdateAppInput is the input for setting an app as default for a type
type UpdateAppInput struct {
	AppID           AppID
	Name            string
	Description     *string
	Default         bool
	Metadata        *map[string]string
	AppConfigUpdate AppConfigUpdate
}

func (i UpdateAppInput) Validate() error {
	if err := i.AppID.Validate(); err != nil {
		return fmt.Errorf("error validating app ID: %w", err)
	}

	// Required fields
	if i.Name == "" {
		return errors.New("name is required")
	}

	if i.Metadata != nil {
		for k, v := range *i.Metadata {
			if k == "" {
				return errors.New("metadata key is required")
			}

			if v == "" {
				return errors.New("metadata value is required")
			}
		}
	}

	if i.AppConfigUpdate != nil {
		if err := i.AppConfigUpdate.Validate(); err != nil {
			return fmt.Errorf("error validating app entity update: %w", err)
		}
	}

	return nil
}

// CreateAppInput is the input for creating an app
type CreateAppInput struct {
	// AppID is optional. If not provided, a new AppID will be generated by the database
	ID          *AppID
	Namespace   string
	Name        string
	Description string
	Type        AppType
}

func (i CreateAppInput) Validate() error {
	if i.Namespace == "" {
		return errors.New("namespace is required")
	}

	if i.Name == "" {
		return errors.New("name is required")
	}

	return nil
}

// ListAppInput is the input for listing installed apps
type ListAppInput struct {
	Namespace string
	pagination.Page

	AppIDs         []AppID
	Type           *AppType
	IncludeDeleted bool
	// Only list apps that has data for the given customer
	CustomerID *customer.CustomerID
}

func (i ListAppInput) Validate() error {
	var errs []error

	if i.Namespace == "" {
		errs = append(errs, models.NewGenericValidationError(
			errors.New("namespace is required"),
		))
	}

	if i.CustomerID != nil {
		if err := i.CustomerID.Validate(); err != nil {
			errs = append(errs, models.NewGenericValidationError(
				fmt.Errorf("error validating customer id: %w", err),
			))
		}

		if i.CustomerID.Namespace != i.Namespace {
			errs = append(errs, models.NewGenericValidationError(
				fmt.Errorf("customer id namespace %s does not match app namespace %s", i.CustomerID.Namespace, i.Namespace),
			))
		}
	}

	if len(i.AppIDs) > 0 {
		for _, appID := range i.AppIDs {
			if appID.Namespace != i.Namespace {
				errs = append(errs, models.NewGenericValidationError(
					fmt.Errorf("app id namespace %s does not match app namespace %s", appID.Namespace, i.Namespace),
				))
			}

			if err := appID.Validate(); err != nil {
				errs = append(errs, models.NewGenericValidationError(
					fmt.Errorf("error validating app id: %w", err),
				))
			}
		}
	}

	return errors.Join(errs...)
}

// UpdateAppStatusInput is the input for updating an app status
type UpdateAppStatusInput struct {
	ID     AppID
	Status AppStatus
}

func (i UpdateAppStatusInput) Validate() error {
	if err := i.ID.Validate(); err != nil {
		return err
	}

	if i.Status == "" {
		return errors.New("status is required")
	}

	return nil
}
